﻿/*****************************
 * A .Net Wrapper around the Reed Solomon Code implementation found in this project
 * Copyright Jake Tober, Free Software Foundation, Inc 2010
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE. 
 *
 * Commercial licensing is available under a separate license, please
 * contact author for details.
 *
 * Source code is available at http://code.google.com/p/raid-anything/
 ******************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;

namespace ReedSolomonNet
{
    /// <summary>
    /// A wrapper class for the ReedSolomon logic
    /// </summary>
    public class ReedSolomon
    {
        private byte[] workingCopy;

        private int _numberOfParityBytes;
        private int _NPAR2;

        /// <summary>
        /// The maximum number of bytes that can be processed at once (256)
        /// </summary>
        /// <remarks>
        /// This value includes both the data and the redundant bits
        /// </remarks>
        public const int MaxLength = 256;

        /// <summary>
        /// Constructs a ReedSolomon wrapper object that will encode/decode streams with the specified number of parity bytes
        /// </summary>
        /// <param name="numberOfParityBytes"></param>
        public ReedSolomon(int numberOfParityBytes)
        {
            Contract.Requires(numberOfParityBytes > 0);
            Contract.Requires(numberOfParityBytes <= 128, "Maximum number of parity bytes is 128");

            _numberOfParityBytes = numberOfParityBytes;
            _NPAR2 = numberOfParityBytes * 2;
            rs = new RSCode.RS(_numberOfParityBytes);
            workingCopy = new byte[256];
        }
        private RSCode.RS rs;

        /// <summary>
        /// Returns the number of parity bits used for encoding, or expected when decoding
        /// </summary>
        public int NumberOfParityBytes
        {
            get
            {
                Contract.Ensures(Contract.Result<int>() > 0);
                Contract.Ensures(Contract.Result<int>() <= 128);
                return _numberOfParityBytes;
            }
        }

        /// <summary>
        /// Encodes a byte array with RS redundency bits
        /// </summary>
        /// <param name="source">The source array</param>
        /// <returns>A new array containing the original data plus redundeny information at the end</returns>
        public byte[] Encode(byte[] source)
        {
            Contract.Requires(source != null);
            Contract.Requires(source.Length > 0);
            Contract.Ensures(Contract.Result<byte[]>() != null);
            Contract.Ensures(Contract.Result<byte[]>().Length == source.Length + NumberOfParityBytes);

            byte[] temp = new byte[source.Length + _numberOfParityBytes];
            Encode(source, temp);
            return temp;
        }
        /// <summary>
        /// Encodes a bytes array with RS redundency bits
        /// </summary>
        /// <param name="source">The source array</param>
        /// <param name="dest">The destination array</param>
        public void Encode(byte[] source, byte[] dest)
        {
            Contract.Requires(source != null);
            Contract.Requires(source.Length > 0);
            Contract.Requires(dest != null);
            Contract.Requires( dest.Length >= source.Length + NumberOfParityBytes);
            Contract.Requires(source.Length + NumberOfParityBytes <= MaxLength, "You can only process 256 bytes at a time");

            rs.Reset();
            rs.encode_data(source, source.Length, workingCopy, _numberOfParityBytes);
            Array.Copy(workingCopy, dest, source.Length + _numberOfParityBytes);
        }
        /// <summary>
        /// Decodes a byte array with RS redundency bits
        /// </summary>
        /// <param name="encodedSource">The encoded source array that contains both data and redundent bits</param>
        /// <returns>A new array with only the data bytes</returns>
        public byte[] Decode(byte[] encodedSource)
        {
            Contract.Requires(encodedSource != null);
            Contract.Requires(encodedSource.Length > this.NumberOfParityBytes);
            Contract.Ensures(Contract.Result<byte[]>() != null);
            Contract.Ensures(Contract.Result<byte[]>().Length == encodedSource.Length - NumberOfParityBytes);
            return Decode(encodedSource, null);
        }
        /// <summary>
        /// Decodes a byte array with RS redundency bits
        /// </summary>
        /// <param name="encodedSource">The encoded source array that contains both data and redundent bits</param>
        /// <param name="knownErasures">A collection of 0-based locations where the byte is missing or corrupt</param>
        /// <returns>A new array with only the data bytes</returns>
        public byte[] Decode(byte[] encodedSource, ICollection<int> knownErasures)
        {
            Contract.Requires(encodedSource != null);
            Contract.Requires(encodedSource.Length > this.NumberOfParityBytes);
            Contract.Ensures(Contract.Result<byte[]>() != null);
            Contract.Ensures(Contract.Result<byte[]>().Length == encodedSource.Length - NumberOfParityBytes);
            byte[] temp = new byte[encodedSource.Length - _numberOfParityBytes];
            Decode(encodedSource, knownErasures, temp);
            return temp;
        }
        /// <summary>
        /// Decodes a byte array with RS redundency bits
        /// </summary>
        /// <param name="encodedSource">The encoded source array that contains both data and redundent bits</param>
        /// <param name="knownErasures">A collection of 0-based locations where the byte is missing or corrupt</param>
        /// <param name="dest">An array to store the decoded information in</param>
        public void Decode(byte[] encodedSource, ICollection<int> knownErasures, byte[] dest)
        {
            Contract.Requires(encodedSource != null);
            Contract.Requires(encodedSource.Length > NumberOfParityBytes, "The encoded source is null or too short");
            Contract.Requires(dest != null); 
            Contract.Requires(dest.Length >= encodedSource.Length - NumberOfParityBytes, "The destination buffer is null or is not large enough to store the decoded data");
            Contract.Requires(encodedSource.Length <= MaxLength, "You can only process 256 bytes at a time");
            Contract.Requires(knownErasures.Count <= NumberOfParityBytes, "There are too many errors present");
            Array.Copy(encodedSource, workingCopy, encodedSource.Length);

            rs.Reset();
            rs.decode_data(workingCopy, encodedSource.Length, _numberOfParityBytes);
            if (rs.check_syndrome(_numberOfParityBytes) != 0)
            {
                int nerasures = 0;
                int[] erasures = null;
                if (knownErasures != null)
                {
                    nerasures = knownErasures.Count;
                    erasures = new int[knownErasures.Count];
                    int i = 0;
                    foreach (int erasure in knownErasures)
                    {
                        erasures[i] = (encodedSource.Length) - erasure - 1;
                        i++;
                    }
                }
                rs.correct_errors_erasures(workingCopy,
                             encodedSource.Length,
                             nerasures,
                             erasures, _NPAR2, _numberOfParityBytes);
            }
            Array.Copy(workingCopy, dest, encodedSource.Length - _numberOfParityBytes);
        }
    }
}
